/**
 * @file lcd.c
 * @brief LCD液晶显示驱动文件
 * @details 实现TFT液晶屏的初始化、显示控制、绘图等功能
 * @author 深圳市全动电子技术有限公司
 * @date 2018/08/09
 * @version V1.0
 * 
 * 本程序只供学习使用，未经作者许可，不得用于其它任何用途
 * 测试硬件：单片机STM32F407ZGT6,正点原子Explorer STM32F4开发板,主频168MHZ，晶振12MHZ
 * QDtech-TFT液晶驱动 for STM32 IO模拟
 * 
 * 公司网站:www.qdtft.com
 * 淘宝网站：http://qdtech.taobao.com
 * wiki技术网站：http://www.lcdwiki.com
 * 
 * 技术支持：
 * 固话(传真) :+86 0755-23594567 
 * 手机:15989313508（冯工） 
 * 邮箱:lcdwiki01@gmail.com    support@lcdwiki.com    goodtft@163.com 
 * 技术支持QQ:3002773612  3002778157
 * 技术交流QQ群:324828016
 * 
 * Copyright(C) 深圳市全动电子技术有限公司 2018-2028
 * All rights reserved
 */
/****************************************************************************************************
//=========================================电源接线================================================//
//     LCD模块                STM32单片机
//      VCC          接        DC5V/3.3V      //电源
//      GND          接          GND          //电源地
//=======================================液晶屏数据线接线==========================================//
//本模块默认数据总线类型为SPI总线
//     LCD模块                STM32单片机    
//    SDI(MOSI)      接          PA7           //液晶屏SPI总线数据写信号
//    SDO(MISO)      接                    //液晶屏SPI总线数据读信号，如果不需要读，可以不接线
//=======================================液晶屏控制线接线==========================================//
//     LCD模块 					      STM32单片机 
//       LED         接          PB13         //液晶屏背光控制信号，如果不需要控制，接5V或3.3V
//       SCK         接          PA5          //液晶屏SPI总线时钟信号
//      DC/RS        接          PA2         //液晶屏数据/命令控制信号
//       RST         接          PB12         //液晶屏复位控制信号
//       CS          接          PA3         //液晶屏片选控制信号
//=========================================触摸屏触接线=========================================//
//如果模块不带触摸功能或者带有触摸功能，但是不需要触摸功能，则不需要进行触摸屏接线
//	   LCD模块                STM32单片机 
//      T_IRQ        未接          PB1          //触摸屏触摸中断信号
//      T_DO         未接          PB2          //触摸屏SPI总线读信号
//      T_DIN        未接          PF11         //触摸屏SPI总线写信号
//      T_CS         未接          PC5          //触摸屏片选控制信号
//      T_CLK        未接          PB0          //触摸屏SPI总线时钟信号
**************************************************************************************************/		
 /* @attention
  *
  * THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
  * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE
  * TIME. AS A RESULT, QD electronic SHALL NOT BE HELD LIABLE FOR ANY
  * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING
  * FROM THE CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE
  * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
**************************************************************************************************/	
#include "lcd.h"              // LCD驱动头文件
#include "stdlib.h"           // 标准库
#include "spi.h"              // SPI驱动头文件
#include "FreeRTOS.h"         // FreeRTOS核心头文件
#include "task.h"             // FreeRTOS任务管理头文件
	   
/* LCD设备管理结构体 */
_lcd_dev lcddev;              // LCD设备参数结构体，默认为竖屏

/* 全局颜色变量 */
u16 POINT_COLOR = 0x0000;     // 画笔颜色（黑色）
u16 BACK_COLOR = 0xFFFF;      // 背景颜色（白色）
u16 DeviceCode;               // 设备代码	 



/**
 * @brief LCD区域填充函数
 * @param sx 起始X坐标
 * @param sy 起始Y坐标
 * @param ex 结束X坐标
 * @param ey 结束Y坐标
 * @param color 填充颜色
 * @param pixel_data 像素数据数组（用于LVGL）
 * @details 填充指定区域的像素数据，支持单色填充和像素数组模式
 */
void LCD_Fill(u16 sx, u16 sy, u16 ex, u16 ey, u16 color, const lv_color_t *pixel_data) 
{
    u32 total_pixels = (ex - sx + 1) * (ey - sy + 1);  // 计算总像素数
    
    // 只设置一次窗口
    LCD_SetWindows(sx, sy, ex, ey);  // 设置显示窗口
    LCD_CS_CLR;                      // 片选信号拉低
    LCD_RS_SET;                      // 进入数据模式

    if (pixel_data == NULL) {
        // 单色填充模式
        for (u32 i = 0; i < total_pixels; i++) {
            SPI0_write_byte(color >> 8);      // 发送高字节
            SPI0_write_byte(color & 0xFF);     // 发送低字节
        }
    } else {
        // 像素数组模式（用于 LVGL）
        for (u32 i = 0; i < total_pixels; i++) {
            SPI0_write_byte(pixel_data->full >> 8);    // 发送高字节
            SPI0_write_byte(pixel_data->full & 0xFF);  // 发送低字节
            pixel_data++;                              // 指向下一个像素
        }
    }

    LCD_CS_SET;  // 片选信号拉高，结束传输
    LCD_SetWindows(0, 0, lcddev.width-1, lcddev.height-1);  // 恢复默认窗口（可选）
}
/**
 * @brief 向LCD写入8位命令
 * @param data 要写入的命令值
 * @details 通过SPI向LCD发送命令字节
 */
void LCD_WR_REG(u8 data)
{ 
   LCD_CS_CLR;     // 片选信号拉低
   LCD_RS_CLR;     // 命令/数据选择信号拉低（命令模式）
   SPI0_write_byte(data);  // 通过SPI发送命令字节
   LCD_CS_SET;     // 片选信号拉高
}

/**
 * @brief 向LCD写入8位数据
 * @param data 要写入的数据值
 * @details 通过SPI向LCD发送数据字节
 */
void LCD_WR_DATA(u8 data)
{
   LCD_CS_CLR;     // 片选信号拉低
   LCD_RS_SET;     // 命令/数据选择信号拉高（数据模式）
   SPI0_write_byte(data);  // 通过SPI发送数据字节
   LCD_CS_SET;     // 片选信号拉高
}

/**
 * @brief 向LCD寄存器写入数据
 * @param LCD_Reg 寄存器地址
 * @param LCD_RegValue 要写入的数据
 * @details 先写入寄存器地址，再写入数据值
 */
void LCD_WriteReg(u8 LCD_Reg, u16 LCD_RegValue)
{	
	LCD_WR_REG(LCD_Reg);      // 写入寄存器地址
	LCD_WR_DATA(LCD_RegValue); // 写入数据值
}	   

/**
 * @brief 准备写入GRAM（图形存储器）
 * @details 发送写入GRAM的命令
 */
void LCD_WriteRAM_Prepare(void)
{
	LCD_WR_REG(lcddev.wramcmd);  // 发送写入GRAM命令
}	 

/**
 * @brief 向LCD写入16位数据
 * @param Data 要写入的16位数据
 * @details 通过SPI向LCD发送16位数据（高字节在前）
 */
void Lcd_WriteData_16Bit(u16 Data)
{	
   LCD_CS_CLR;           // 片选信号拉低
   LCD_RS_SET;           // 命令/数据选择信号拉高（数据模式）
   SPI0_write_byte(Data>>8);  // 发送高字节
   SPI0_write_byte(Data);     // 发送低字节
   LCD_CS_SET;           // 片选信号拉高
}

/**
 * @brief 在指定位置绘制一个像素点
 * @param x 像素的X坐标
 * @param y 像素的Y坐标
 * @details 设置光标位置并写入像素颜色数据
 */
void LCD_DrawPoint(u16 x,u16 y)
{
	LCD_SetCursor(x,y);           // 设置光标位置
	Lcd_WriteData_16Bit(POINT_COLOR);  // 写入像素颜色数据
}

/**
 * @brief 全屏填充LCD屏幕
 * @param Color 填充颜色
 * @details 将整个LCD屏幕填充为指定颜色
 */
void LCD_Clear(u16 Color)
{
  unsigned int i,m;  
	LCD_SetWindows(0,0,lcddev.width-1,lcddev.height-1);   // 设置全屏窗口
	LCD_CS_CLR;  // 片选信号拉低
	LCD_RS_SET;  // 命令/数据选择信号拉高（数据模式）
	
	/* 逐像素填充颜色 */
	for(i=0;i<lcddev.height;i++) {
    for(m=0;m<lcddev.width;m++) {	
			Lcd_WriteData_16Bit(Color);  // 写入颜色数据
		}
	}
	LCD_CS_SET;  // 片选信号拉高
} 

/**
 * @brief 初始化LCD屏幕GPIO
 * @details 配置LCD控制引脚为输出模式
 */
void LCD_GPIOInit(void)
{
	/* 使能GPIO时钟 */
	rcu_periph_clock_enable(RCU_GPIOA);  // 使能GPIOA时钟
	rcu_periph_clock_enable(RCU_GPIOB);  // 使能GPIOB时钟
	
	/* 配置背光控制引脚 */
	gpio_mode_set(LED_PORT,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,LED_PIN);  // 设置为输出模式，上拉
	gpio_output_options_set(LED_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, LED_PIN);  // 推挽输出，最大速度
	
	/* 配置片选引脚 */
	gpio_mode_set(CS_PORT,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,CS_PIN);  // 设置为输出模式，上拉
	gpio_output_options_set(CS_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, CS_PIN);  // 推挽输出，最大速度
	
	/* 配置数据/命令选择引脚 */
	gpio_mode_set(DC_PORT,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,DC_PIN);  // 设置为输出模式，上拉
	gpio_output_options_set(DC_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, DC_PIN);  // 推挽输出，最大速度
	
	/* 配置复位引脚 */
	gpio_mode_set(RST_PORT,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,RST_PIN);  // 设置为输出模式，上拉
	gpio_output_options_set(RST_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, RST_PIN);  // 推挽输出，最大速度
}

/**
 * @brief 复位LCD屏幕
 * @details 通过复位引脚对LCD进行硬件复位
 */
void LCD_RESET(void)
{
	LCD_RST_CLR;      // 复位引脚拉低
	vTaskDelay(100);  // 延时100ms
	LCD_RST_SET;      // 复位引脚拉高
	vTaskDelay(50);   // 延时50ms
}

/**
 * @brief 初始化LCD屏幕
 * @details 初始化SPI、GPIO，配置LCD寄存器，设置显示方向
 */
void LCD_Init(void)
{  
	spi_Init();        // 硬件SPI初始化
	LCD_GPIOInit();    // LCD GPIO初始化										 
 	LCD_RESET();       // LCD 复位
	
	/* ILI9341 LCD控制器初始化序列 */
	LCD_WR_REG(0xCF);  
	LCD_WR_DATA(0x00); 
	LCD_WR_DATA(0xD9); 
	LCD_WR_DATA(0X30); 
	LCD_WR_REG(0xED);  
	LCD_WR_DATA(0x64); 
	LCD_WR_DATA(0x03); 
	LCD_WR_DATA(0X12); 
	LCD_WR_DATA(0X81); 
	LCD_WR_REG(0xE8);  
	LCD_WR_DATA(0x85); 
	LCD_WR_DATA(0x10); 
	LCD_WR_DATA(0x7A); 
	LCD_WR_REG(0xCB);  
	LCD_WR_DATA(0x39); 
	LCD_WR_DATA(0x2C); 
	LCD_WR_DATA(0x00); 
	LCD_WR_DATA(0x34); 
	LCD_WR_DATA(0x02); 
	LCD_WR_REG(0xF7);  
	LCD_WR_DATA(0x20); 
	LCD_WR_REG(0xEA);  
	LCD_WR_DATA(0x00); 
	LCD_WR_DATA(0x00); 
	LCD_WR_REG(0xC0);    // Power control 
	LCD_WR_DATA(0x1B);   // VRH[5:0] 
	LCD_WR_REG(0xC1);    // Power control 
	LCD_WR_DATA(0x12);   // SAP[2:0];BT[3:0] 0x01
	LCD_WR_REG(0xC5);    // VCM control 
	LCD_WR_DATA(0x08); 	 // 30
	LCD_WR_DATA(0x26); 	 // 30
	LCD_WR_REG(0xC7);    // VCM control2 
	LCD_WR_DATA(0XB7); 
	LCD_WR_REG(0x36);    // Memory Access Control 
	LCD_WR_DATA(0x08); 
	LCD_WR_REG(0x3A);   
	LCD_WR_DATA(0x55); 
	LCD_WR_REG(0xB1);   
	LCD_WR_DATA(0x00);   
	LCD_WR_DATA(0x1A); 
	LCD_WR_REG(0xB6);    // Display Function Control 
	LCD_WR_DATA(0x0A); 
	LCD_WR_DATA(0xA2); 
	LCD_WR_REG(0xF2);    // 3Gamma Function Disable 
	LCD_WR_DATA(0x00); 
	LCD_WR_REG(0x26);    // Gamma curve selected 
	LCD_WR_DATA(0x01); 
	LCD_WR_REG(0xE0);    // Set Gamma 
	LCD_WR_DATA(0x0F); 
	LCD_WR_DATA(0x1D); 
	LCD_WR_DATA(0x1A); 
	LCD_WR_DATA(0x0A); 
	LCD_WR_DATA(0x0D); 
	LCD_WR_DATA(0x07); 
	LCD_WR_DATA(0x49); 
	LCD_WR_DATA(0X66); 
	LCD_WR_DATA(0x3B); 
	LCD_WR_DATA(0x07); 
	LCD_WR_DATA(0x11); 
	LCD_WR_DATA(0x01); 
	LCD_WR_DATA(0x09); 
	LCD_WR_DATA(0x05); 
	LCD_WR_DATA(0x04); 		 
	LCD_WR_REG(0XE1);    // Set Gamma 
	LCD_WR_DATA(0x00); 
	LCD_WR_DATA(0x18); 
	LCD_WR_DATA(0x1D); 
	LCD_WR_DATA(0x02); 
	LCD_WR_DATA(0x0F); 
	LCD_WR_DATA(0x04); 
	LCD_WR_DATA(0x36); 
	LCD_WR_DATA(0x13); 
	LCD_WR_DATA(0x4C); 
	LCD_WR_DATA(0x07); 
	LCD_WR_DATA(0x13); 
	LCD_WR_DATA(0x0F); 
	LCD_WR_DATA(0x2E); 
	LCD_WR_DATA(0x2F); 
	LCD_WR_DATA(0x05); 
	LCD_WR_REG(0x2B); 
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0x01);
	LCD_WR_DATA(0x3f);
	LCD_WR_REG(0x2A); 
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0xef);	 
	LCD_WR_REG(0x11);    // Exit Sleep
	vTaskDelay(120);
	LCD_WR_REG(0x29);    // display on		

	LCD_direction(USE_HORIZONTAL);  // 设置LCD显示方向
	LCD_LED_SET;                    // 点亮背光	 
	LCD_Clear(WHITE);               // 清全屏白色
}
 
/**
 * @brief 设置LCD显示窗口
 * @param xStar LCD显示窗口起始X坐标
 * @param yStar LCD显示窗口起始Y坐标
 * @param xEnd LCD显示窗口结束X坐标
 * @param yEnd LCD显示窗口结束Y坐标
 * @details 设置LCD显示区域，准备写入GRAM
 */
void LCD_SetWindows(u16 xStar, u16 yStar,u16 xEnd,u16 yEnd)
{	
	/* 设置X坐标范围 */
	LCD_WR_REG(lcddev.setxcmd);	       // 发送设置X坐标命令
	LCD_WR_DATA(xStar>>8);             // 发送起始X坐标高字节
	LCD_WR_DATA(0x00FF&xStar);         // 发送起始X坐标低字节
	LCD_WR_DATA(xEnd>>8);              // 发送结束X坐标高字节
	LCD_WR_DATA(0x00FF&xEnd);          // 发送结束X坐标低字节

	/* 设置Y坐标范围 */
	LCD_WR_REG(lcddev.setycmd);	       // 发送设置Y坐标命令
	LCD_WR_DATA(yStar>>8);             // 发送起始Y坐标高字节
	LCD_WR_DATA(0x00FF&yStar);         // 发送起始Y坐标低字节
	LCD_WR_DATA(yEnd>>8);              // 发送结束Y坐标高字节
	LCD_WR_DATA(0x00FF&yEnd);          // 发送结束Y坐标低字节

	LCD_WriteRAM_Prepare();	           // 开始写入GRAM
}   

/**
 * @brief 设置坐标值
 * @param Xpos 像素的X坐标
 * @param Ypos 像素的Y坐标
 * @details 设置光标位置到指定坐标
 */
void LCD_SetCursor(u16 Xpos, u16 Ypos)
{	  	    			
	LCD_SetWindows(Xpos,Ypos,Xpos,Ypos);	 // 设置窗口为单个像素点
} 

/**
 * @brief 设置LCD屏幕显示方向
 * @param direction 显示方向：0-0度，1-90度，2-180度，3-270度
 * @details 根据方向设置LCD的宽度、高度和显示控制寄存器
 */
void LCD_direction(u8 direction)
{ 
	/* 设置命令寄存器地址 */
	lcddev.setxcmd=0x2A;  // 设置X坐标命令
	lcddev.setycmd=0x2B;  // 设置Y坐标命令
	lcddev.wramcmd=0x2C;  // 写入GRAM命令
	
	switch(direction){		  
		case 0:						 	 		// 0度方向
			lcddev.width=LCD_W;               // 宽度为LCD_W
			lcddev.height=LCD_H;              // 高度为LCD_H
			LCD_WriteReg(0x36,(1<<3)|(0<<6)|(0<<7));  // BGR==1,MY==0,MX==0,MV==0
		break;
		case 1:                              // 90度方向
			lcddev.width=LCD_H;              // 宽度为LCD_H
			lcddev.height=LCD_W;             // 高度为LCD_W
			LCD_WriteReg(0x36,(1<<3)|(0<<7)|(1<<6)|(1<<5));  // BGR==1,MY==1,MX==0,MV==1
		break;
		case 2:						 	 		// 180度方向
			lcddev.width=LCD_W;              // 宽度为LCD_W
			lcddev.height=LCD_H;             // 高度为LCD_H
			LCD_WriteReg(0x36,(1<<3)|(1<<6)|(1<<7));  // BGR==1,MY==1,MX==1,MV==0
		break;
		case 3:                              // 270度方向
			lcddev.width=LCD_H;              // 宽度为LCD_H
			lcddev.height=LCD_W;             // 高度为LCD_W
			LCD_WriteReg(0x36,(1<<3)|(1<<7)|(1<<5));  // BGR==1,MY==1,MX==0,MV==1
		break;	
		default:break;
	}		
}	 
